const fs = require("fs-extra");
const path = require("path");
const crypto = require("crypto");
const https = require("https");
const http = require("http");
const config = require("../config"); // 添加config引用

/**
 * 精简的缓存管理器 - 只保留核心流式缓存功能
 */
class CacheManager {
  constructor(config) {
    // 加密配置 - 可选择禁用以提升性能
    this.encryptionEnabled = process.env.CACHE_ENCRYPTION !== 'false'; // 默认启用，可通过环境变量禁用
    if (this.encryptionEnabled) {
      const keyString = "pointcloud-cache-proxy-2024-secret-key-32";
      // 确保密钥是32字节（AES-256要求）
      this.encryptionKey = crypto.createHash('sha256').update(keyString).digest();
    }
    this.cacheDir = config.cache.dir;
    this.ttl = config.cache.ttl;
    this.maxSize = this.parseSize(config.cache.maxSize);
    this.periodicCleanupEnabled = config.cache.periodicCleanup !== false;
    this.pathPattern = config.cache.pathPattern;

    // LRU缓存访问记录 - 内存中维护
    this.lruCache = new Map(); // key -> { lastAccess, size, hits }
    this.totalCacheSize = 0;

    // 确保缓存目录存在
    fs.ensureDirSync(this.cacheDir);

    // 启动时初始化LRU状态
    this.initializeLRU();

  }





  /**
   * 初始化LRU状态 - 启动时扫描现有缓存文件
   */
  async initializeLRU() {
    try {
      const files = await fs.readdir(this.cacheDir);
      const cacheFiles = files.filter((f) => f.endsWith(".cache"));

      this.lruCache.clear();
      this.totalCacheSize = 0;

      for (const file of cacheFiles) {
        try {
          const cacheKey = file.replace(".cache", "");
          const filePath = path.join(this.cacheDir, file);
          const metaPath = path.join(this.cacheDir, `${cacheKey}.meta`);

          if (await fs.pathExists(metaPath)) {
            const [stats, metadata] = await Promise.all([
              fs.stat(filePath),
              this.readEncryptedMetadata(metaPath),
            ]);

            this.lruCache.set(cacheKey, {
              lastAccess: stats.atime.getTime(),
              size: stats.size,
              hits: 0,
              created: metadata.timestamp || stats.ctime.getTime(),
            });

            this.totalCacheSize += stats.size;
          }
        } catch (error) {
          // 忽略损坏的缓存项
        }
      }

    } catch (error) {
      console.warn("LRU初始化失败:", error.message);
    }
  }

  /**
   * 更新LRU访问记录
   */
  updateLRU(cacheKey, size = 0, isNewFile = false) {
    const now = Date.now();
    const existing = this.lruCache.get(cacheKey);

    if (existing) {
      // 更新现有记录 - 优化：只更新访问时间和命中次数，不移动位置
      existing.lastAccess = now;
      existing.hits++;
    } else if (isNewFile) {
      // 新文件
      this.lruCache.set(cacheKey, {
        lastAccess: now,
        size: size,
        hits: 1,
        created: now,
      });
      this.totalCacheSize += size;
    }
  }

  /**
   * LRU算法清理缓存
   */
  async lruEviction(targetSize) {

    // 转换为数组并按LRU顺序排序
    const entries = Array.from(this.lruCache.entries());
    entries.sort((a, b) => {
      // 优先级: 最近访问时间 > 命中次数 > 创建时间
      const [keyA, dataA] = a;
      const [keyB, dataB] = b;

      // 最近最少使用的排在前面
      if (dataA.lastAccess !== dataB.lastAccess) {
        return dataA.lastAccess - dataB.lastAccess;
      }

      // 命中次数少的排在前面
      if (dataA.hits !== dataB.hits) {
        return dataA.hits - dataB.hits;
      }

      // 创建时间早的排在前面
      return dataA.created - dataB.created;
    });

    let currentSize = this.totalCacheSize;
    let cleanedFiles = 0;
    let cleanedSize = 0;

    for (const [cacheKey, data] of entries) {
      if (currentSize <= targetSize) break;

      try {
        // 删除文件
        const cacheFilePath = this.getCacheFilePath(cacheKey);
        const metadataPath = this.getMetadataPath(cacheKey);

        await Promise.all([fs.remove(cacheFilePath), fs.remove(metadataPath)]);

        // 更新LRU状态
        this.lruCache.delete(cacheKey);
        currentSize -= data.size;
        cleanedSize += data.size;
        cleanedFiles++;

      } catch (error) {
        console.warn(`LRU删除失败 ${cacheKey}:`, error.message);
      }
    }

    this.totalCacheSize = currentSize;


    return { cleanedFiles, cleanedSize, currentSize };
  }

  /**
   * 加密数据 - 使用正确的AES-256-CBC实现
   */
  encryptData(data) {
    try {
      const iv = crypto.randomBytes(16); // 16字节初始向量
      const cipher = crypto.createCipheriv("aes-256-cbc", this.encryptionKey, iv);

      let encrypted = cipher.update(data);
      encrypted = Buffer.concat([encrypted, cipher.final()]);

      // 返回: IV(16字节) + 加密数据
      return Buffer.concat([iv, encrypted]);
    } catch (error) {
      console.error("❌ 加密失败:", error.message);
      throw error; // 加密失败应该抛出错误，而不是静默降级
    }
  }

  /**
   * 流式解密数据 - 只解密到指定位置以优化Range请求
   */
  decryptDataWithLimit(encryptedData, maxDecryptBytes = null) {
    try {
      if (encryptedData.length < 16) {
        return encryptedData;
      }

      const iv = encryptedData.slice(0, 16);
      const encrypted = encryptedData.slice(16);

      const decipher = crypto.createDecipheriv("aes-256-cbc", this.encryptionKey, iv);
      
      // 设置不自动填充，我们手动处理
      decipher.setAutoPadding(false);

      let decrypted = Buffer.alloc(0);
      let processedBytes = 0;
      
      // 如果没有限制，使用原有逻辑
      if (!maxDecryptBytes) {
        let result = decipher.update(encrypted);
        result = Buffer.concat([result, decipher.final()]);
        return result;
      }
      
      // 流式解密，按块处理
      const blockSize = 16;
      let offset = 0;
      
      while (offset < encrypted.length && processedBytes < maxDecryptBytes) {
        // 计算这次处理的块大小
        const remainingEncrypted = encrypted.length - offset;
        const remainingNeeded = maxDecryptBytes - processedBytes;
        
        // 至少处理一个完整的AES块
        const chunkSize = Math.min(
          Math.ceil(remainingNeeded / blockSize) * blockSize + blockSize, // 确保有额外缓冲
          remainingEncrypted
        );
        
        if (chunkSize <= 0) break;
        
        const chunk = encrypted.slice(offset, offset + chunkSize);
        const decryptedChunk = decipher.update(chunk);
        
        decrypted = Buffer.concat([decrypted, decryptedChunk]);
        processedBytes = decrypted.length;
        offset += chunkSize;
        
        // 如果已经解密了足够的数据，可以停止
        if (processedBytes >= maxDecryptBytes) {
          break;
        }
      }
      
      // 如果需要，处理最后的填充
      if (offset >= encrypted.length) {
        try {
          const final = decipher.final();
          decrypted = Buffer.concat([decrypted, final]);
        } catch (error) {
          // 填充错误，可能是部分解密导致的，忽略
        }
      }
      
      return decrypted;
      
    } catch (error) {
      console.warn("⚠️ 流式解密失败，使用完整解密:", error.message);
      return this.decryptData(encryptedData);
    }
  }

  /**
   * 解密数据 - 使用正确的AES-256-CBC实现
   */
  decryptData(encryptedData) {
    try {
      if (encryptedData.length < 16) {
        // 数据太短，可能不是加密数据，直接返回
        return encryptedData;
      }

      const iv = encryptedData.slice(0, 16);
      const encrypted = encryptedData.slice(16);

      const decipher = crypto.createDecipheriv("aes-256-cbc", this.encryptionKey, iv);

      let decrypted = decipher.update(encrypted);
      decrypted = Buffer.concat([decrypted, decipher.final()]);

      return decrypted;
    } catch (error) {
      console.warn("⚠️ 解密失败，可能是未加密数据:", error.message);
      return encryptedData; // 解密失败时返回原始数据（向后兼容）
    }
  }


  /**
   * 解析大小字符串
   */
  parseSize(sizeStr) {
    const units = { B: 1, KB: 1024, MB: 1024 ** 2, GB: 1024 ** 3 };
    const match = sizeStr.match(/^(\d+(?:\.\d+)?)\s*([A-Z]*B?)$/i);
    if (!match) return 1024 ** 3; // 默认1GB

    const [, size, unit] = match;
    return Math.floor(parseFloat(size) * (units[unit.toUpperCase()] || 1));
  }

  /**
   * 检查路径是否需要缓存
   */
  shouldCache(url) {
    return url.includes(this.pathPattern);
  }

  /**
   * 生成缓存键 - 忽略S3签名参数和Range相关参数
   * 确保相同文件无论以何种方式请求都有相同的缓存键
   */
  generateCacheKey(url, params = {}) {
    try {
      const urlObj = new URL(url, "http://localhost");

      // 移除S3签名相关的查询参数和其他时变参数
      const ignoreParams = [
        "X-Amz-Algorithm",
        "X-Amz-Date",
        "X-Amz-SignedHeaders",
        "X-Amz-Expires",
        "X-Amz-Credential",
        "X-Amz-Signature",
        "X-Amz-Security-Token",
        "_t",
        "timestamp",
        "t",
        "time", // 常见的时间戳参数
        "v",
        "version", // 版本参数
        "r",
        "random",
        "rand", // 随机数参数
        "nocache",
        "nc", // 缓存破坏参数
        "_", // jQuery等库常用的随机参数
      ];

      // 使用更智能的参数过滤 - 直接匹配所有X-Amz-开头的参数
      const paramsToDelete = [];
      for (const [paramName] of urlObj.searchParams.entries()) {
        const paramLower = paramName.toLowerCase();

        // 检查是否是S3相关参数（X-Amz-开头）或其他时变参数
        const isS3Param = paramLower.startsWith("x-amz-");
        const isOtherIgnoreParam = ignoreParams.some(
          (ignore) => paramLower === ignore.toLowerCase()
        );

        if (isS3Param || isOtherIgnoreParam) {
          paramsToDelete.push(paramName);
        }
      }

      // 删除需要忽略的参数
      paramsToDelete.forEach((param) => {
        urlObj.searchParams.delete(param);
      });

      const cleanedParams = Array.from(urlObj.searchParams.entries());

      // 使用路径+剩余查询参数生成键
      const cleanUrl = urlObj.pathname + urlObj.search;

      // 特别处理：确保即使没有剩余参数，URL格式也一致
      const finalUrl = cleanedParams.length === 0 ? urlObj.pathname : cleanUrl;

      // 简化缓存键生成 - 只使用清理后的URL，忽略额外参数
      const key = finalUrl;
      const cacheKey = crypto.createHash("sha256").update(key).digest("hex");

      return cacheKey;
    } catch (error) {
      // 降级方案：使用原始URL
      const key = `${url}_${JSON.stringify(params)}`;
      const cacheKey = crypto.createHash("sha256").update(key).digest("hex");

      return cacheKey;
    }
  }

  /**
   * 获取缓存文件路径
   */
  getCacheFilePath(cacheKey) {
    return path.join(this.cacheDir, `${cacheKey}.cache`);
  }


  /**
   * 检查缓存是否有效
   */
  async isCacheValid(cacheKey) {
    try {
      const cacheFilePath = this.getCacheFilePath(cacheKey);
      if (!(await fs.pathExists(cacheFilePath))) {
        return false;
      }

      // 简单检查：文件存在且在TTL内（基于文件修改时间）
      const stats = await fs.stat(cacheFilePath);
      const now = Date.now();
      return now - stats.mtime.getTime() < this.ttl;
    } catch (error) {
      return false;
    }
  }

  /**
   * 从缓存读取数据 - 智能处理加密和Range请求
   */
  async getFromCache(cacheKey, range = null) {
    try {
      const cacheFilePath = this.getCacheFilePath(cacheKey);

      if (!(await fs.pathExists(cacheFilePath))) {
        this.lruCache.delete(cacheKey);
        return null;
      }

      const perfStart = Date.now();
      const readStart = Date.now();
      
      if (range) {
        // Range请求：假设未加密，直接读取
        const fileHandle = await require('fs').promises.open(cacheFilePath, 'r');
        try {
          const fileSize = (await fs.stat(cacheFilePath)).size;
          const actualEnd = range.end !== undefined ? range.end : fileSize - 1;
          const rangeLength = actualEnd - range.start + 1;
          
          const rangeBuffer = Buffer.allocUnsafe(rangeLength);
          await fileHandle.read(rangeBuffer, 0, rangeLength, range.start);
          
          const readTime = Date.now() - readStart;
          const totalTime = Date.now() - perfStart;
          
          if (config.logging.enabled) {
            console.log(`⏱️ Range优化读取: 总${totalTime}ms (读取${readTime}ms)`);
          }
          
          range.end = actualEnd;
          this.updateLRU(cacheKey, fileSize, false);
          
          return {
            data: rangeBuffer,
            headers: { 'content-type': 'application/octet-stream' },
            status: 200,
            isRange: true,
            range: range
          };
          
        } finally {
          await fileHandle.close();
        }
      } else {
        // 完整文件读取 - 可能需要解密
        let fileData = await fs.readFile(cacheFilePath);
        const readTime = Date.now() - readStart;
        
        // 尝试解密（如果是加密文件）
        let decryptTime = 0;
        if (this.encryptionEnabled) {
          const decryptStart = Date.now();
          try {
            const decryptedData = this.decryptData(fileData);
            fileData = decryptedData;
            decryptTime = Date.now() - decryptStart;
          } catch (error) {
            // 解密失败，可能是未加密文件，使用原始数据
            decryptTime = 0;
          }
        }
        
        const totalTime = Date.now() - perfStart;
        
        if (config.logging.enabled) {
          console.log(`⏱️ 完整文件读取: 总${totalTime}ms (读取${readTime}ms, 解密${decryptTime}ms)`);
        }

        this.updateLRU(cacheKey, fileData.length, false);
        return {
          data: fileData,
          headers: { 'content-type': 'application/octet-stream' },
          status: 200,
        };
      }
    } catch (error) {
      return null;
    }
  }

  /**
   * 保存缓存 - Range请求不加密，其他根据配置加密
   */
  async saveToCache(cacheKey, url, headers = {}) {
    const cacheFilePath = this.getCacheFilePath(cacheKey);
    const fileName = this.getSimpleFileName(url);

    return new Promise((resolve, reject) => {
      try {
        const proto = url.startsWith("https") ? https : http;
        const fileStream = fs.createWriteStream(cacheFilePath);
        let receivedBytes = 0;

        const urlObj = new URL(url);
        const options = {
          hostname: urlObj.hostname,
          port: urlObj.port,
          path: urlObj.pathname + urlObj.search,
          method: "GET",
          headers: {
            "User-Agent": "Mozilla/5.0 (PointCloud Cache Proxy)",
          },
        };

        const request = proto.request(options, (res) => {
          if (res.statusCode !== 200) {
            fileStream.destroy();
            fs.unlink(cacheFilePath, () => {});
            reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
            return;
          }

          res.on("data", (chunk) => {
            receivedBytes += chunk.length;
          });

          res.pipe(fileStream);

          fileStream.on("finish", async () => {
            try {
              // 检查是否为Range请求 - Range请求不加密
              const isRangeRequest = headers["x-has-range"] === "true";
              const shouldEncrypt = this.encryptionEnabled && !isRangeRequest;
              
              if (shouldEncrypt) {
                // 读取原始数据，加密后重新写入
                const originalData = await fs.readFile(cacheFilePath);
                const encryptedData = this.encryptData(originalData);
                await fs.writeFile(cacheFilePath, encryptedData);
                
                if (config.logging.enabled) {
                  console.log(`🔐 加密缓存: ${fileName} (${this.formatSize(receivedBytes)})`);
                }
              } else {
                if (config.logging.enabled) {
                  const status = isRangeRequest ? 'Range未加密' : '未加密';
                  console.log(`📦 ${status}缓存: ${fileName} (${this.formatSize(receivedBytes)})`);
                }
              }

              this.updateLRU(cacheKey, receivedBytes, true);
              await this.checkCacheSizeWithLRU();
              resolve(true);
            } catch (error) {
              await this.removeCacheFiles(cacheKey);
              reject(error);
            }
          });

          fileStream.on("error", async (err) => {
            await fs.unlink(cacheFilePath, () => {});
            reject(err);
          });
        });

        request.on("error", async (err) => {
          await fs.unlink(cacheFilePath, () => {});
          reject(err);
        });

        request.setTimeout(120000);
        request.on("timeout", () => {
          request.destroy();
          fs.unlink(cacheFilePath, () => {});
          reject(new Error("请求超时"));
        });

        request.end();
      } catch (error) {
        reject(error);
      }
    });
  }

  /**
   * 使用LRU算法检查缓存大小并清理
   */
  async checkCacheSizeWithLRU() {
    if (this.totalCacheSize > this.maxSize) {
      const targetSize = this.maxSize * 0.8; // 清理到80%
      await this.lruEviction(targetSize);
    }
  }



  /**
   * 删除缓存文件
   */
  async removeCacheFiles(cacheKey) {
    await fs.remove(this.getCacheFilePath(cacheKey));
  }

  /**
   * 格式化文件大小
   */
  formatSize(bytes) {
    const units = ["B", "KB", "MB", "GB"];
    let size = bytes;
    let unitIndex = 0;

    while (size >= 1024 && unitIndex < units.length - 1) {
      size /= 1024;
      unitIndex++;
    }

    return `${size.toFixed(1)} ${units[unitIndex]}`;
  }

  /**
   * 智能流式Range读取 - 只读取解密所需的最小数据
   * 对于AES-CBC加密文件，需要从文件开头读取到Range结束位置，但可以避免读取文件尾部
   */
  async streamingRangeRead(cacheFilePath, range, totalFileSize) {
    const readStart = Date.now();
    
    try {
      // AES-CBC特点：
      // 1. 需要16字节IV（文件开头）
      // 2. 需要从开头连续解密到目标位置
      // 3. 16字节块对齐，可能需要额外数据
      
      // 计算需要读取的数据量
      const ivSize = 16; // AES IV大小
      const blockSize = 16; // AES块大小
      
      // Range结束位置（在原始数据中）
      const rangeEndInOriginal = range.end;
      
      // 估算加密后需要读取的最大位置
      // 加密数据 = IV + 加密块 + 填充
      // 保守估计：读取到 rangeEnd + 一些缓冲区
      const bufferSize = blockSize * 2; // 额外缓冲区
      const estimatedEncryptedEnd = ivSize + rangeEndInOriginal + bufferSize;
      
      // 不能超过文件实际大小
      const readSize = Math.min(estimatedEncryptedEnd, totalFileSize);
      
      if (config.logging.enabled) {
        console.log(`🔍 智能读取策略: Range要求${range.start}-${range.end} (${this.formatSize(range.end - range.start + 1)}), 预计读取${this.formatSize(readSize)}`);
      }
      
      // 如果读取大小与文件大小相近（差异<10%），直接读取整个文件
      const sizeDiff = totalFileSize - readSize;
      const diffPercentage = sizeDiff / totalFileSize;
      
      if (diffPercentage < 0.1) {
        // 差异很小，直接读取整个文件更高效
        const encryptedData = await fs.readFile(cacheFilePath);
        const readTime = Date.now() - readStart;
        
        if (config.logging.enabled) {
          console.log(`📄 差异过小(${(diffPercentage * 100).toFixed(1)}%)，读取完整文件更高效`);
        }
        
        return {
          encryptedData,
          readTime,
          savedBytes: 0,
          strategy: 'full-file'
        };
      }
      
      // 执行部分读取
      const fileHandle = await fs.open(cacheFilePath, 'r');
      const buffer = Buffer.allocUnsafe(readSize);
      await fileHandle.read(buffer, 0, readSize, 0);
      await fileHandle.close();
      
      const readTime = Date.now() - readStart;
      const savedBytes = totalFileSize - readSize;
      
      if (config.logging.enabled) {
        console.log(`⚡ 部分读取成功: ${this.formatSize(readSize)}/${this.formatSize(totalFileSize)} (节省${this.formatSize(savedBytes)})`);
      }
      
      return {
        encryptedData: buffer,
        readTime,
        savedBytes,
        strategy: 'partial-read'
      };
      
    } catch (error) {
      // 降级到完整文件读取
      console.warn(`⚠️ 智能读取失败，降级到完整读取: ${error.message}`);
      const encryptedData = await fs.readFile(cacheFilePath);
      const readTime = Date.now() - readStart;
      
      return {
        encryptedData,
        readTime,
        savedBytes: 0,
        strategy: 'fallback-full'
      };
    }
  }

  /**
   * 获取简化的文件名用于日志
   */
  getSimpleFileName(url) {
    if (!url || url === "unknown") return "unknown";
    try {
      const parsed = new URL(url, "http://localhost");
      const pathParts = parsed.pathname.split("/").filter((p) => p);
      const fileName = pathParts[pathParts.length - 1] || "unknown";
      return fileName.length > 50
        ? fileName.substring(0, 47) + "..."
        : fileName;
    } catch {
      return url.length > 50 ? url.substring(0, 47) + "..." : url;
    }
  }
}

module.exports = CacheManager;
